<?php

namespace App\Services\Repositories\SkinRecord;

use App\Http\Requests\Wechat\SkinRecordListRequest;
use App\Models\OpenWechat\Authorizer;
use App\Models\OpenWechat\WechatUser;
use App\Models\Panx\SkinDetector;
use App\Models\Store\HmSkinRecord;
use App\Models\Store\SkinRecord;
use App\Models\Store\Store;
use App\Models\Store\StorePivotWechatUser;
use App\Models\Store\WaxSkinRecord;
use App\Services\Presenters\HmSkinData;
use App\Services\Presenters\SkinRecordPresenter;
use App\Services\Presenters\WaxSkinData;
use App\Services\Utils\Guard;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Facades\DB;


/**
 * 肤测记录
 * Class SkinRecordRepository
 * @package App\Services\SkinRecord
 */
class SkinRecordRepository
{
    /**
     * @var SkinRecord
     */
    protected $skin_record;

    public function __construct()
    {
        $this->skin_record = new SkinRecord;
    }

    /**
     * 获取图像与接触测肤数据
     *
     * @param $skin_detector_id
     * @param $skin_record_ids
     * @param int $customer_id
     * @return array
     */
    public function getSkinRecords($skin_detector_id, $skin_record_ids, $customer_id = 0): array
    {
        if ($skin_detector_id == SkinDetector::ID_HM) { // 接触
//            $skin_records = $this->assembleHmSkinRecordsData($skin_record_ids);
            $skin_records = $this->assembleHmSkinRecordsDataByCustom($skin_record_ids, $customer_id);
        } else { // wax
//            $skin_records = $this->assembleWaxSkinRecordsData($skin_record_ids);
            $skin_records = $this->assembleWaxSkinRecordsDataByCustom($skin_record_ids, $customer_id);
        }

        return $skin_records;
    }

    /**
     * @param array $store_ids
     *
     * @return Builder
     */
    public static function latestStoreSkinRecordsGroupBy(array $store_ids): Builder
    {
        return (new SkinRecord)
            ->whereIn('store_id', $store_ids)
            ->select([
                'wechat_user_id', DB::raw('MAX(created_at) AS created_at')
            ])
            ->groupBy('wechat_user_id');
    }

    /**
     * 获取满足条件的微信用户测肤数据所有记录id
     *
     * @param WechatUser $wechat_user
     * @param int $skin_detector_id
     *
     * @return Collection
     */
    public function searchByWechatAndSkinDetector(WechatUser $wechat_user,
                                                  int $skin_detector_id): Collection
    {
        $request = request();

        $skin_records = (new SkinRecord)
            ->where('skin_detector_id', $skin_detector_id)
            ->where('wechat_user_id', $wechat_user->id)
            ->when($request->customer_id, function (Builder $builder, $customer_id) {
                $builder->whereIn('store_id', $this->getStoreIdsByCustomerId($customer_id));
            })
            ->when($request->store_id, function (Builder $builder, $store_id) {
                $builder->where('store_id', $store_id);
            })
            ->latest()
            ->when($request->get('take'), function (Builder $query, $take) { //
                $query->take($take);
            })
            ->get();

        return $skin_records;
    }

    /**
     * @param $customer_id
     * @return array|int[]
     */
    private function getStoreIdsByCustomerId($customer_id): array
    {
        return (new Store())->where('customer_id', $customer_id)->pluck('id')->all();
    }

    /**
     *
     * 获取满足条件的微信用户测肤数据所有记录id (列表详情)
     *
     * @param int $skin_detector_id
     * @param WechatUser $wechat_user
     * @return mixed|array
     */
    public function getSkinRecordIdsForList(int $skin_detector_id, WechatUser $wechat_user): array
    {
        if ($skin_detector_id == SkinRecord::WAX) {
            $skin_record_ids = $wechat_user->waxSkinRecords->pluck('skin_record_id')->all();
        } else {
            $skin_record_ids = $wechat_user->hmSkinRecords->pluck('skin_record_id')->all();
        }
        return $skin_record_ids;
    }

    /**
     * @param $skin_detector_id
     * @param $customer_id
     * @return \App\Models\Customer\WaxSkinSuggest[]|\Illuminate\Database\Eloquent\Builder[]|\Illuminate\Database\Eloquent\Collection
     */
    public function getCustomerSkinSuggests($skin_detector_id, $customer_id)
    {
        if ((int)$skin_detector_id === SkinRecord::HM) {
            $skin_suggests = HmSkinData::getHmSkinSuggest($customer_id);
        } else {
            $skin_suggests = WaxSkinData::getWaxSkinSuggest($customer_id);
        }

        return $skin_suggests;
    }

    /**
     * @param SkinRecordListRequest $request
     * @param WechatUser $wechat_user
     * @param array $skin_record_ids
     * @param array $store_ids
     * @param Collection $skin_suggests
     * @return LengthAwarePaginator
     */
    public function normalizeSkinRecordList(SkinRecordListRequest $request,
                                             WechatUser $wechat_user,
                                             array $skin_record_ids,
                                             array $store_ids, Collection $skin_suggests): LengthAwarePaginator
    {

        $skin_records = $this->querySkinRecords($request, $wechat_user, $skin_record_ids, $store_ids);

        $skin_records->transform(function (SkinRecord $skinRecord) use($skin_suggests){
            return $this->transformSkinRecordByType($skinRecord, $skin_suggests);
        });

        return $skin_records;
    }

    /**
     * @param SkinRecord $skinRecord
     *
     * @param Collection $skin_suggests
     * @return SkinRecord
     */
    private function transformSkinRecordByType(SkinRecord $skinRecord, Collection $skin_suggests): SkinRecord
    {
        if ($skinRecord->isHuiMei()) {
            $this->transformHmRecords($skinRecord, $skin_suggests);
        }

        if ($skinRecord->isWax()) {
            $this->transformWaxRecords($skinRecord, $skin_suggests);
        }

        return $skinRecord;
    }

    /**
     * @param SkinRecord $skinRecord
     * @param Collection $skin_suggests
     */
    private function transformHmRecords(SkinRecord $skinRecord, Collection $skin_suggests): void
    {
        $skinRecord->hmSkinRecords->transform(function (HmSkinRecord $hmSkinRecord) use($skin_suggests){
            return SkinRecordPresenter::assembleDetailHmSkinRecordByCustom($hmSkinRecord, $skin_suggests);
        });
    }

    /**
     * @param SkinRecord $skinRecord
     * @param Collection $skin_suggests
     */
    private function transformWaxRecords(SkinRecord $skinRecord, Collection $skin_suggests): void
    {
        /**
         * 由于查询 skinRecords 时使用了 with('waxSkinRecords')
         * 这里 $skinRecord->wax_skin_records 直接赋值会被关联方法 waxSkinRecords 覆盖，
         * 所以后面取消了关联关系 unsetRelation，才能正确显示按 UI 需求的排序
         */
        $skinRecord->wax_skin_records = $skinRecord->waxSkinRecords
            ->transform(function (WaxSkinRecord $waxSkinRecord) use($skin_suggests){
                return SkinRecordPresenter::assembleDetailWaxSkinRecordByCustom($waxSkinRecord, $skin_suggests);
            })
            ->sortBy(function (WaxSkinRecord $rec) {
                return WaxSkinRecord::ITEMSID_SORT[$rec->itemsid];
            })
            ->values(); // 不加 values 排序也是错的

        $skinRecord->unsetRelation('waxSkinRecords');
    }

    /**
     * @param SkinRecordListRequest $request
     * @param $wechat_user
     * @param $skin_record_ids
     * @param $store_ids
     *
     * @return mixed
     */
    private function querySkinRecords(SkinRecordListRequest $request,
                                      WechatUser $wechat_user,
                                      array $skin_record_ids,
                                      array $store_ids): LengthAwarePaginator
    {
        $time_range = [$request->testing_start_time, $request->testing_end_time];

        $skin_records = (new SkinRecord)
            ->where('wechat_user_id', $wechat_user->id)
            ->whereIn('id', $skin_record_ids)
            ->whereIn('store_id', $store_ids)
            ->filterStoreKeyword($request->keyword)
            ->filterTimeRange($time_range)
            ->with(['store', 'waxSkinRecords', 'hmSkinRecords'])
            ->when($request->customer_id, function (Builder $builder, $customer_id) {
                $builder->whereIn('store_id', $this->getStoreIdsByCustomerId($customer_id));
            })
            ->when($request->store_id, function (Builder $builder, $store_id) {
                $builder->where('store_id', $store_id);
            })
            ->orderBy('created_at', 'DESC')
            ->paginate($request->per_page ?: 15);

        return $skin_records;
    }

    /**
     * @param SkinRecord $skinRecord
     *
     * @return bool
     */
    public static function isAllSubSkinRecordsDone(SkinRecord $skinRecord): bool
    {
        switch ($skinRecord->skin_detector_id) {
            case SkinRecord::WAX:
                $done = self::isWaxRecordsDone($skinRecord);
                break;
            case SkinRecord::HM:
                $done = self::isHmRecordsDone($skinRecord);
                break;
            default :
                $done = false;
                break;
        }

        return $done;
    }


    /**
     * 检查维普思是否全部检测完
     *
     * @param SkinRecord $skinRecord
     * @return bool
     */
    public static function isWaxRecordsDone(SkinRecord $skinRecord): bool
    {
        $all_positions = WaxSkinRecord::ITEMSID_ALL;
        $items = (new WaxSkinRecord)
            ->where('skin_record_id', $skinRecord->id)
            ->whereIn('itemsid', $all_positions)
            ->pluck('itemsid')
            ->unique()->values()->all();

        sort($items);
        sort($all_positions);

        return $all_positions === $items;
    }

    /**
     * 检查会美是否全部检测完
     * @param SkinRecord $skinRecord
     * @return bool
     */
    public static function isHmRecordsDone(SkinRecord $skinRecord): bool
    {
        $all_positions = HmSkinRecord::ALL_POSITIONS;

        $pos = (new HmSkinRecord)
            ->where('skin_record_id', $skinRecord->id)
            ->whereIn('position', $all_positions)
            ->pluck('position')
            ->unique()->values()->all();

        sort($pos);
        sort($all_positions);

        return $all_positions === $pos;
    }


    /**
     * 后台客户/店铺统计测肤数量
     *
     * @param array $store_ids
     * @param string $group_type
     * @param array|null $time_range
     * @return Visit[]|\Illuminate\Database\Eloquent\Collection
     * @throws \Exception
     */
    public function queryCustomerOrStoreSkinRecords(array $store_ids,
                                                    String $group_type,
                                                    array $time_range = null)
    {

        $group_sql = $this->groupSqlByType($group_type);

        $visit_counts = $this->skin_record
            ->select(
                DB::raw('COUNT(*) AS count'),
                DB::raw('MAX(created_at) AS end_at'),
                DB::raw('MIN(created_at) AS start_at'),
                DB::raw($group_sql['select']))
            ->where(function(Builder $query) use($store_ids){
                $this->queryStoresSkinRecord($query, $store_ids);
            })
            ->groupBy($group_sql['groupBy'])
            ->orderBys($group_sql['orderBys'])
            ->when($time_range, function (Builder $builder, $created_at_range) {
                $builder->whereBetween('created_at', $created_at_range);
            })
            ->get();

        return $visit_counts;
    }

    /**
     * @param Carbon $date
     * @return array
     */
    public function formatStartEndDateRange(Carbon $date): array
    {
        $skin_record_date = [
            'start' => [
                'year'  => $date->year,
                'month' => $date->month,
                'day'   => $date->day,
            ],
            'end'   => [
                'year'  => now()->year,
                'month' => now()->month,
                'day'   => now()->day,
            ],
        ];
        return $skin_record_date;
    }

    /**
     * @param string $group_type
     *
     * @link https://blog.csdn.net/spokenInChina/article/details/49680251 - Mysql日期函数使用汇总
     *
     * @return array
     * @throws \Exception
     */
    private function groupSqlByType(string $group_type): array
    {
        switch (mb_strtolower($group_type)) {
            case 'year' :
                return $this->groupSqlYear();
            case 'month' :
                return $this->groupSqlYearMonth();
            case 'week' :
                return $this->groupSqlYearWeek();
            case 'day' :
                return $this->groupSqlYearMonthDay();
            default :
                throw new \Exception('Invalid group type given');
        }
    }

    private function groupSqlYear(): array
    {
        return $sql = [
            'select'  => "YEAR(created_at) AS year", // year / month / week 不用 `` 也不会报错，什么情况使用关键字会报错呢？
            'groupBy' => "year",
            'orderBys' => [
                ['order_by' => 'year', 'sort' => 'desc']
            ],
        ];
    }

    /**
     * @return array
     */
    private function groupSqlYearMonth(): array
    {
        return $sql = [
            'select'  => "YEAR(created_at) AS `year`, MONTH(created_at) AS `month`",
            // groupBy 多个字段和 distinct 很像，year + month 的组合才合并成一组
            'groupBy' => ['year', 'month'],
            'orderBys' => [
                ['order_by' => 'year', 'sort' => 'desc'],
                ['order_by' => 'month', 'sort' => 'desc']
            ],
        ];
    }

    private function groupSqlYearWeek(): array
    {
        return $sql = [
            'select'  => "YEAR(created_at) AS `year`, WEEK(created_at) AS `week`",
            'groupBy' => ['year', 'week'],
            'orderBys' => [
                ['order_by' => 'year', 'sort' => 'desc'],
                ['order_by' => 'week', 'sort' => 'desc']
            ],
        ];
    }

    private function groupSqlYearMonthDay(): array
    {
        return $sql = [
            'select'  => "YEAR(created_at) AS `year`, MONTH(created_at) AS `month`, DAY(created_at) AS `day`",
            'groupBy' => ['year', 'month', 'day'],
            'orderBys' => [
                ['order_by' => 'year', 'sort' => 'desc'],
                ['order_by' => 'month', 'sort' => 'desc'],
                ['order_by' => 'day', 'sort' => 'desc'],
            ],
        ];
    }

    /**
     * @param Builder $query
     * @param array $store_ids
     * @return Builder
     */
    private function queryStoresSkinRecord(Builder $query, array $store_ids)
    {
        return $query->whereIn('store_id',$store_ids);
    }

    /**
     * @param array $store_ids
     *
     * @return Collection
     */
    /**
     * @return SkinRecord
     */
    public function getSkinRecord(): SkinRecord
    {
        return $this->skin_record;
    }

    /**
     * @param array $store_ids
     *
     * @return array
     */
    public function storesUsageCountInfo(array $store_ids): array
    {
        $skin_records = $this->latestStoresSkinRecordsGroupByWxUserAndSkinDetector($store_ids);

        $all_count = $skin_records->groupBy('wechat_user_id')->count();
        $hm_count = $skin_records->where('skin_detector_id', SkinRecord::HM)->count();
        $wax_count = $skin_records->where('skin_detector_id', SkinRecord::WAX)->count();

        $latest_record_created_at = $skin_records
                ->last()
                ->created_at ?? now();
        $skin_record_date = $this->formatStartEndDateRange($latest_record_created_at);

        return compact('all_count', 'hm_count', 'wax_count', 'skin_record_date');
    }

    /**
     * @param array $store_ids
     *
     * @return Collection
     */
    public function latestStoresSkinRecordsGroupByWxUserAndSkinDetector(array $store_ids): Collection
    {
        // 本来不需要过滤店铺对应微信用户的，
        // 数据库中有一些错误的数据， skin_records 表 wechat_user_id 比如 37 有很多数据
        // 但 store_pivot_wechat_user 表 wechat_user_id 为 37 却只有一条与 store_id 为 1 的关联数据
        // 导致只用 $store_ids 过滤会出现的话会出现比 WechatController::index 返回用户列表多的情况
        // 问题出在数据库表 store_pivot_wechat_user 与 skin_records 不一致，应该去修正数据库的数据关联
        // 线上已同步，这里暂时不修改
        $stores_wx_user_ids = StorePivotWechatUser::storesWechatUsersIds($store_ids);

        $skin_records = $this->skin_record
            ->selectRaw('wechat_user_id, skin_detector_id, MAX(created_at) as created_at')
            ->whereIn('store_id', $store_ids)
            ->whereIn('wechat_user_id', $stores_wx_user_ids)
            ->whereIn('skin_detector_id', [SkinRecord::HM, SkinRecord::WAX])
            ->groupBy('wechat_user_id', 'skin_detector_id')
            ->latest()
            ->get();

        return $skin_records;
    }

    /**
     * @param $skin_record_ids
     * @return array
     */
    private function assembleHmSkinRecordsData($skin_record_ids): array
    {
        $positions = HmSkinRecord::POSITIONS_LOCALE_MAP[trans('app.locale_language')];

        /** @var Collection|HmSkinRecord[] $hm_records */
        $hm_records = HmSkinRecord::whereIn('skin_record_id', $skin_record_ids)->oldest()->get();

        $skin_records = [];
        foreach ($positions as $pos_idx => $pos_name) {
            $skin_records[] = [
                'key'          => $pos_idx,
                'value'        => $pos_name,
                'skin_records' => $hm_records
                    ->where('position', $pos_idx)
                    ->values()
                    ->transform(function (HmSkinRecord $hm_skin_record) {
                        return SkinRecordPresenter::assembleDetailHmSkinRecord($hm_skin_record);
                    }),
            ];
        }
        return $skin_records;
    }

    /**
     * @param $skin_record_ids
     * @param $customer_id
     * @return array
     */
    private function assembleHmSkinRecordsDataByCustom($skin_record_ids, $customer_id): array
    {
        $positions = HmSkinRecord::ALL_POSITIONS;

        /** @var Collection|HmSkinRecord[] $hm_records */
        $hm_records = (new HmSkinRecord)->whereIn('skin_record_id', $skin_record_ids)->oldest()->get();
        $hm_skin_suggests = HmSkinData::getHmSkinSuggest($customer_id);

        $skin_records = [];
        foreach ($positions as $pos_idx) {
            $skin_records[] = [
                'key'          => $pos_idx,
                'value'        => trans('admin.hm_position_'.$pos_idx),
                'skin_records' => $hm_records
                    ->where('position', $pos_idx)
                    ->values()
                    ->transform(function (HmSkinRecord $hm_skin_record) use($hm_skin_suggests){
                        return SkinRecordPresenter::assembleDetailHmSkinRecordByCustom($hm_skin_record, $hm_skin_suggests);
                    }),
            ];
        }
        return $skin_records;
    }

    /**
     * @deprecated - 用下面的：assembleWaxSkinRecordsDataByCustom
     * @param $skin_record_ids
     * @return array
     */
    private function assembleWaxSkinRecordsData($skin_record_ids): array
    {
        $wax_items = WaxSkinRecord::ITEM_LOCALE_MAP[trans('app.locale_language')];

        /** @var Collection|WaxSkinRecord[] $wax_records */
        $wax_records = WaxSkinRecord::whereIn('skin_record_id', $skin_record_ids)->oldest()->get();

        $skin_records = [];
        foreach ($wax_items as $item_sid => $item_name) {
            $skin_records[] = [
                'key'          => $item_sid,
                'value'        => $item_name,
                'skin_records' => $wax_records
                    ->where('itemsid', $item_sid)
                    ->values()
                    ->transform(function (WaxSkinRecord $wax_record) {
                        if (Authorizer::isRequestAppidMuw()) {
                            return SkinRecordpresenter::assembleMuwDetailWaxSkinRecord($wax_record);
                        } else {
                            return SkinRecordpresenter::assembleDetailWaxSkinRecord($wax_record);
                        }
                    }),
            ];
        }
        return $skin_records;
    }

    /**
     * @param $skin_record_ids
     * @param $customer_id
     * @return array
     */
    private function assembleWaxSkinRecordsDataByCustom($skin_record_ids, $customer_id): array
    {
        $wax_items = WaxSkinRecord::ITEMSID_ALL;

        /** @var Collection|WaxSkinRecord[] $wax_records */
        $wax_records = WaxSkinRecord::whereIn('skin_record_id', $skin_record_ids)->oldest()->get();
        $wax_skin_suggests = WaxSkinData::getWaxSkinSuggest($customer_id);

        $skin_records = [];
        foreach ($wax_items as $item_sid) {
            $skin_records[] = [
                'key'          => $item_sid,
                'value'        => trans('admin.wax_itemsid_'.$item_sid),
                'skin_records' => $wax_records
                    ->where('itemsid', $item_sid)
                    ->values()
                    ->transform(function (WaxSkinRecord $wax_record) use($wax_skin_suggests){
                        return SkinRecordpresenter::assembleDetailWaxSkinRecordByCustom($wax_record, $wax_skin_suggests);
                    }),
            ];
        }
        return $skin_records;
    }
}
